package handler

import (
	"cloud-stm32-cld-platform/config"
	"cloud-stm32-cld-platform/cons"
	"cloud-stm32-cld-platform/model"
	"cloud-stm32-cld-platform/service"
	"cloud-stm32-cld-platform/utils"
	"mime/multipart"
	"path"
	"strconv"

	"net/http"

	"github.com/gin-gonic/gin"
)

func Register(r *gin.Engine) {
	r.GET("/ping", healthCheck())

	r.POST("/v1/keilprjzip", uploadZipFile()) // 上传文件后点击“确定”，在服务器端解压缩项目文件

	r.POST("/v1/chipBurning", chipBuring()) // 点击“烧录”按钮，执行烧录

	r.GET("/v1/projects", listAllProjects())
}

func healthCheck() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	}
}

func uploadZipFile() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		paras, err_msg := validateUnzipParas(ctx)
		if err_msg != "" || len(paras) == 0 {
			ctx.String(http.StatusInternalServerError, err_msg)
			return
		}
		prjName, prjUser, saveDur, fileData :=
			paras[0].(string), paras[1].(string), paras[2].(int), paras[3].(*multipart.FileHeader)

		cachePath, err := utils.GenPathBaseRTime(cons.DefCnfPath_PrjCache, prjName, prjUser)
		if err != nil {
			ctx.String(http.StatusInternalServerError, err.Error())
			return
		}
		savedFile := path.Join(cachePath, fileData.Filename)
		if err := ctx.SaveUploadedFile(fileData, savedFile); err != nil {
			ctx.String(http.StatusInternalServerError, err.Error())
			return
		}

		// TODO fulfill prj status and build time
		prj_uuid := savePrjModel(prjName, prjUser, saveDur)
		unzipHandler := SaveUnzipModel(savedFile, cachePath)
		var need_clear bool

		defer func() {
			if need_clear {
				_ = erasePrjModel(prj_uuid)
				eraseUnzipModel()
			}
		}()

		zes := service.NewExecSvc(unzipHandler)
		err = zes.UnzipPrj(ctx)
		if err != nil {
			need_clear = true
			ctx.String(http.StatusInternalServerError, "unzip project failed")
			return
		}

		ctx.JSON(http.StatusOK, gin.H{
			"message": "success",
		})
	}
}

func chipBuring() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		var prj_unicode string
		err_msg := validateBurningParas(ctx, &prj_unicode)
		if err_msg != "" {
			ctx.String(http.StatusInternalServerError, err_msg)
			return
		}

		// keep the project locked status
		prjMdl := config.GlobalConfigCenter.GetPrjModel(prj_unicode)
		if prjMdl == nil {
			ctx.String(http.StatusInternalServerError, "project is missed")
			return
		}
		if prjMdl.Status >= model.PRJ_BURNING {
			ctx.String(http.StatusInternalServerError, "project has been using or expired")
			return
		}

		buildHandler := saveKeilModel(prj_unicode)
		if err := service.NewExecSvc(buildHandler).CompileAndLink(ctx); err != nil {
			_ = config.GlobalConfigCenter.UpdatePrjModel(prj_unicode, "Status", model.PRJ_COMPILE_FAILED)
			ctx.String(http.StatusInternalServerError, "compiling and linking faild")
			return
		}

		buildHandler.Convert2Download()
		if err := service.NewExecSvc(buildHandler).ChipBurning(ctx); err != nil {
			_ = config.GlobalConfigCenter.UpdatePrjModel(prj_unicode, "Status", model.PRJ_BURN_FAILED)
			ctx.String(http.StatusInternalServerError, "burning code failed")
			return
		}
		_ = config.GlobalConfigCenter.UpdatePrjModel(prj_unicode, "Status", model.PRJ_BURNED)
		ctx.JSON(http.StatusOK, gin.H{
			"message": "success",
		})
	}
}

func listAllProjects() gin.HandlerFunc {
	return func(ctx *gin.Context) {

	}
}

func SaveUnzipModel(savedFile, cachePath string) *model.FileModel {
	unzipHandler := &model.FileModel{
		KeilFileModel: nil,
		UnzipFileModel: &model.UnzipFileModel{
			BaseFile: model.NewBaseFile(
				cons.DefCnfCmd_Unzip,
				savedFile,
				cachePath,
			),
			UnZipFileOption: model.NewUnZipFileOption(),
		},
	}
	config.GlobalConfigCenter.WriteCmdModelConf(unzipHandler)
	return unzipHandler
}

func saveKeilModel(prj_unicode string) *model.FileModel {
	prjModel := config.GlobalConfigCenter.GetPrjModel(prj_unicode)
	logFile, err := utils.GenLogPath(prjModel.Path)
	if err != nil {
		return nil
	}

	keilHandler := &model.FileModel{
		KeilFileModel: &model.KeilFileModel{
			BaseFile: model.NewBaseFile(
				cons.DefCntCmd_UV4,
				path.Join(prjModel.Path, cons.DefCnfPath_PrjParentPath, prjModel.Name+cons.DefCnfFile_PrjSubfix),
				logFile,
			),
			KeilFileOption: model.NewKeilFileOptionByOpt(
				model.WithKeilFileOptBuild(true),
				model.WithKeilFileOptHideGUI(true),
				model.WithKeilFileOptTarget(prjModel.Name),
				model.WithKeilFileOptOutput(logFile),
			),
		},
		UnzipFileModel: nil,
	}
	config.GlobalConfigCenter.WriteCmdModelConf(keilHandler) // TODO flush old unzipHandler ??
	return keilHandler
}

// TODO
func eraseUnzipModel() {

}

func savePrjModel(prjName, prjUser string, saveDur int) string {
	prj_uuid := utils.GenUUID()
	prjModel := model.NewPrjModelByOpt(
		model.WithPrjModelName(prjName),
		model.WithPrjModelOwner(prjUser),
		model.WithPrjModelDuration(int64(saveDur)),
		model.WithPrjModelUniCode(prj_uuid),
	)
	config.GlobalConfigCenter.SetPrjModel(prj_uuid, prjModel)
	return prj_uuid
}

func erasePrjModel(prj_uuid string) string {
	return config.GlobalConfigCenter.DelPrjModel(prj_uuid)
}

/*
@ ret_val: prjame, prjUser, saveDuration, fileRawData
*/
func validateUnzipParas(ctx *gin.Context) (paras []interface{}, err_msg string) {
	prjName := ctx.PostForm("prj_name")
	prjUser := ctx.PostForm("prj_user")
	saveDur, err := strconv.Atoi(ctx.PostForm("save_duration"))
	if err != nil {
		err_msg = "invalid project save duration"
		return
	}

	var prj_reg model.PARA_TYPE_PRJ
	if !prj_reg.Validate(prjName) || !prj_reg.Validate(prjUser) {
		err_msg = "invalid project name or user, valid format: characters and nums, 3 <= length <= 10"
		return
	}

	fileData, err := ctx.FormFile("raw_file")
	if err != nil {
		err_msg = "get file failed"
		return
	}
	paras = append(paras, prjName, prjUser, saveDur, fileData)
	return
}

func validateBurningParas(ctx *gin.Context, uni *string) string {
	prj_unicode := ctx.Query("prj_unicode")
	if prj_unicode == "" {
		return "empty project uuid"
	}
	var prj_uuid_reg model.PARA_TYPE_UUID
	if !prj_uuid_reg.Validate(prj_unicode) {
		return "invalide project unique code"
	}
	return ""
}
